package com.sqtracker.recommender.algorithm;

import com.sqtracker.recommender.dto.RecommendationRequest;
import com.sqtracker.recommender.dto.RecommendationResponse;
import com.sqtracker.recommender.model.TorrentMetadataExt;
import com.sqtracker.recommender.model.UserBehavior;
import com.sqtracker.recommender.model.UserPreference;
import com.sqtracker.recommender.repository.TorrentMetadataExtRepository;
import com.sqtracker.recommender.repository.UserBehaviorRepository;
import com.sqtracker.recommender.repository.UserPreferenceRepository;
import com.sqtracker.recommender.util.VectorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 基于内容的推荐算法实现类
 *
 * 该算法通过分析用户的历史行为和偏好设置，构建用户兴趣画像，
 * 然后找出与用户兴趣最相似的内容进行推荐。
 *
 * 主要特点：
 * 1. 基于用户显式偏好（用户设置的分类、标签、语言偏好）
 * 2. 基于用户隐式偏好（从历史行为中推断的偏好）
 * 3. 使用余弦相似度计算用户画像与内容特征的匹配度
 * 4. 支持相似内容推荐功能
 *
 * @author sqtracker
 * @version 1.0
 */
@Component
public class ContentBasedRecommender implements RecommendationAlgorithm {

    private static final Logger logger = LoggerFactory.getLogger(ContentBasedRecommender.class);

    // 依赖注入的仓库类
    private final UserBehaviorRepository userBehaviorRepository;
    private final UserPreferenceRepository userPreferenceRepository;
    private final TorrentMetadataExtRepository torrentMetadataExtRepository;

    /**
     * torrent特征向量缓存
     * key: torrentId, value: 该torrent的特征向量（标签、分类、语言等特征的映射）
     * 使用ConcurrentHashMap保证线程安全
     */
    private Map<String, Map<String, Double>> torrentVectors = new ConcurrentHashMap<>();

    /**
     * 构造函数，通过依赖注入初始化各个仓库
     */
    @Autowired
    public ContentBasedRecommender(
            UserBehaviorRepository userBehaviorRepository,
            UserPreferenceRepository userPreferenceRepository,
            TorrentMetadataExtRepository torrentMetadataExtRepository) {
        this.userBehaviorRepository = userBehaviorRepository;
        this.userPreferenceRepository = userPreferenceRepository;
        this.torrentMetadataExtRepository = torrentMetadataExtRepository;
    }

    /**
     * 基于内容的推荐主方法
     *
     * 算法流程：
     * 1. 构建用户兴趣画像（基于偏好设置和历史行为）
     * 2. 获取用户已交互过的内容列表（避免重复推荐）
     * 3. 遍历所有可推荐内容，计算与用户画像的相似度
     * 4. 按相似度排序，返回Top-N推荐结果
     *
     * @param request 推荐请求，包含用户ID和推荐数量
     * @return 推荐响应，包含推荐的torrent列表
     */
    @Override
    public RecommendationResponse recommend(RecommendationRequest request) {
        String userId = request.getUserId();
        int count = request.getCount();
        logger.debug("ContentBased recommending for userId: {}, count: {}", userId, count);

        // 步骤1：构建用户兴趣画像
        Map<String, Double> userProfile = buildUserProfile(userId);

        if (userProfile.isEmpty()) {
            logger.warn("User profile for userId {} is empty. ContentBased returning empty recommendations.", userId);
            return new RecommendationResponse();
        }
        logger.debug("User profile for userId {} built with {} features.", userId, userProfile.size());
        if (logger.isTraceEnabled()) {
            userProfile.forEach((k, v) -> logger.trace("UserProfile Feature: {} -> {}", k, v));
        }

        // 步骤2：获取用户已交互的内容ID集合，避免重复推荐
        Set<String> interactedTorrentIds = userBehaviorRepository.findByUserId(userId)
                .stream()
                .map(UserBehavior::getTorrentId)
                .collect(Collectors.toSet());
        logger.debug("User {} has interacted with {} items.", userId, interactedTorrentIds.size());

        // 步骤3：获取所有可推荐的内容
        List<TorrentMetadataExt> allTorrents = torrentMetadataExtRepository.findAll();
        if (allTorrents.isEmpty()) {
            logger.warn("No torrent metadata found in repository. ContentBased returning empty recommendations.");
            return new RecommendationResponse();
        }
        logger.debug("Total torrents fetched for content-based matching: {}", allTorrents.size());

        // 步骤4：计算相似度分数
        Map<String, Double> scoreMap = new HashMap<>();

        for (TorrentMetadataExt torrent : allTorrents) {
            String torrentId = torrent.getTorrentId();

            // 跳过用户已交互过的内容
            if (interactedTorrentIds.contains(torrentId)) {
                continue;
            }

            // 获取torrent的特征向量
            Map<String, Double> torrentVector = getTorrentVector(torrent);
            if (torrentVector.isEmpty()) {
                logger.warn("Torrent vector for torrentId {} is empty. Skipping.", torrentId);
                continue;
            }

            // 计算用户画像与内容特征的余弦相似度
            double similarity = VectorUtil.cosineSimilarity(userProfile, torrentVector);

            if (similarity > 0.0) {
                scoreMap.put(torrentId, similarity);
            }
        }
        logger.debug("ContentBased score map for userId {}: {} potential recommendations before sorting.", userId, scoreMap.size());

        // 步骤5：按相似度排序并限制数量
        List<Map.Entry<String, Double>> sortedItems = scoreMap.entrySet().stream()
                .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
                .limit(count)
                .collect(Collectors.toList());
        logger.info("ContentBased generated {} recommendations for userId {} after sorting and limiting.", sortedItems.size(), userId);

        // 步骤6：构建并返回推荐响应
        RecommendationResponse response = new RecommendationResponse();
        response.setRecommendations(buildRecommendationItems(sortedItems));
        if (response.getRecommendations().isEmpty() && !userProfile.isEmpty() && !allTorrents.isEmpty()) {
            logger.warn("ContentBased for user {} resulted in empty recommendations despite having a profile and {} total torrents. ScoreMap had {} candidates.", userId, allTorrents.size(), scoreMap.size());
        }
        return response;
    }

    /**
     * 推荐与指定内容相似的内容
     *
     * 基于内容相似度的推荐，找出与目标内容特征最相似的其他内容
     *
     * @param request 推荐请求，包含目标torrentId和推荐数量
     * @return 推荐响应，包含相似内容列表
     */
    public RecommendationResponse recommendSimilar(RecommendationRequest request) {
        String torrentId = request.getTorrentId();
        int count = request.getCount();
        logger.debug("ContentBased recommendSimilar for torrentId: {}, count: {}", torrentId, count);

        // 步骤1：获取目标内容的元数据
        Optional<TorrentMetadataExt> torrentOpt = torrentMetadataExtRepository.findByTorrentId(torrentId);

        if (!torrentOpt.isPresent()) {
            logger.warn("Target torrentId {} not found for similar recommendations.", torrentId);
            return new RecommendationResponse();
        }

        // 步骤2：构建目标内容的特征向量
        TorrentMetadataExt targetTorrent = torrentOpt.get();
        Map<String, Double> targetVector = getTorrentVector(targetTorrent);
        if (targetVector.isEmpty()) {
            logger.warn("Target torrentId {} vector is empty. Cannot find similar items.", torrentId);
            return new RecommendationResponse();
        }
        logger.debug("Target vector for torrentId {} has {} features.", torrentId, targetVector.size());

        // 步骤3：获取所有候选内容
        List<TorrentMetadataExt> allTorrents = torrentMetadataExtRepository.findAll();
        if (allTorrents.isEmpty()) {
            logger.warn("No torrent metadata found in repository for similar recommendations.");
            return new RecommendationResponse();
        }
        logger.debug("Total torrents for similarity comparison: {}", allTorrents.size());

        // 步骤4：计算相似度分数
        Map<String, Double> scoreMap = new HashMap<>();

        for (TorrentMetadataExt torrent : allTorrents) {
            String currTorrentId = torrent.getTorrentId();

            // 跳过目标内容本身
            if (currTorrentId.equals(torrentId)) {
                continue;
            }

            Map<String, Double> torrentVector = getTorrentVector(torrent);
            if (torrentVector.isEmpty()) {
                continue;
            }

            // 计算与目标内容的相似度
            double similarity = VectorUtil.cosineSimilarity(targetVector, torrentVector);

            if (similarity > 0.0) {
                scoreMap.put(currTorrentId, similarity);
            }
        }
        logger.debug("Similar items score map for torrentId {}: {} potential items.", torrentId, scoreMap.size());

        // 步骤5：排序并返回结果
        List<Map.Entry<String, Double>> sortedItems = scoreMap.entrySet().stream()
                .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
                .limit(count)
                .collect(Collectors.toList());
        logger.info("ContentBased generated {} similar recommendations for torrentId {}.", sortedItems.size(), torrentId);

        RecommendationResponse response = new RecommendationResponse();
        response.setRecommendations(buildRecommendationItems(sortedItems));
        return response;
    }

    /**
     * 构建用户兴趣画像
     *
     * 综合用户的显式偏好和隐式偏好（从历史行为推断）构建用户画像
     *
     * @param userId 用户ID
     * @return 用户兴趣画像，key为特征名称（如category_音乐、tag_摇滚等），value为兴趣权重
     */
    private Map<String, Double> buildUserProfile(String userId) {
        Map<String, Double> userProfile = new HashMap<>();
        logger.debug("Building user profile for userId: {}", userId);

        // 步骤1：处理用户显式偏好设置
        Optional<UserPreference> userPrefOpt = userPreferenceRepository.findByUserId(userId);

        if (userPrefOpt.isPresent()) {
            UserPreference userPref = userPrefOpt.get();
            logger.debug("User preferences found for {}. Explicit: {}, Implicit: {}", userId, userPref.getExplicitPreferences() != null, userPref.getImplicitPreferences() != null);

            // 处理显式偏好（用户主动设置的偏好）
            if (userPref.getExplicitPreferences() != null) {
                UserPreference.ExplicitPreferences explicit = userPref.getExplicitPreferences();
                // 添加偏好的分类，权重为1.0
                if (explicit.getCategories() != null) {
                    explicit.getCategories().forEach(c -> userProfile.put("category_" + c, 1.0));
                }
                // 添加偏好的标签，权重为1.0
                if (explicit.getTags() != null) {
                    explicit.getTags().forEach(t -> userProfile.put("tag_" + t, 1.0));
                }
                // 添加偏好的语言，权重为1.0
                if (explicit.getLanguages() != null) {
                    explicit.getLanguages().forEach(l -> userProfile.put("language_" + l, 1.0));
                }
            }

            // 处理隐式偏好（系统推断的偏好，带有权重）
            if (userPref.getImplicitPreferences() != null) {
                UserPreference.ImplicitPreferences implicit = userPref.getImplicitPreferences();
                // 累加隐式偏好的分类权重
                if (implicit.getCategories() != null) {
                    implicit.getCategories().forEach((k, v) -> userProfile.put("category_" + k, userProfile.getOrDefault("category_" + k, 0.0) + v));
                }
                // 累加隐式偏好的标签权重
                if (implicit.getTags() != null) {
                    implicit.getTags().forEach((k, v) -> userProfile.put("tag_" + k, userProfile.getOrDefault("tag_" + k, 0.0) + v));
                }
                // 累加隐式偏好的语言权重
                if (implicit.getLanguages() != null) {
                    implicit.getLanguages().forEach((k, v) -> userProfile.put("language_" + k, userProfile.getOrDefault("language_" + k, 0.0) + v));
                }
            }
        } else {
            logger.debug("No explicit or implicit preferences stored for userId: {}", userId);
        }

        // 步骤2：基于历史行为构建用户画像
        List<UserBehavior> behaviors = userBehaviorRepository.findByUserId(userId);
        logger.debug("Found {} behaviors for userId {} to augment profile.", behaviors.size(), userId);
        for (UserBehavior behavior : behaviors) {
            String torrentId = behavior.getTorrentId();
            double weight = getBehaviorWeight(behavior.getType()); // 根据行为类型获取权重

            Optional<TorrentMetadataExt> torrentOpt = torrentMetadataExtRepository.findByTorrentId(torrentId);
            if (torrentOpt.isPresent()) {
                TorrentMetadataExt torrent = torrentOpt.get();
                // 从用户交互过的内容中提取特征，累加到用户画像中
                if (torrent.getTags() != null) {
                    torrent.getTags().forEach(tag -> userProfile.put("tag_" + tag, userProfile.getOrDefault("tag_" + tag, 0.0) + weight));
                }
                if (torrent.getLanguage() != null) {
                    userProfile.put("language_" + torrent.getLanguage(), userProfile.getOrDefault("language_" + torrent.getLanguage(), 0.0) + weight);
                }
                if (torrent.getTorrentCategoryId() != null) {
                    userProfile.put("category_" + torrent.getTorrentCategoryId(), userProfile.getOrDefault("category_" + torrent.getTorrentCategoryId(), 0.0) + weight);
                }
            }
        }
        logger.info("User profile for {} built with {} features.", userId, userProfile.size());
        return userProfile;
    }

    /**
     * 获取torrent的特征向量
     *
     * 将torrent的各种属性（标签、语言、分类、字幕、质量等）转换为特征向量
     * 使用缓存提高性能
     *
     * @param torrent torrent元数据
     * @return 特征向量，key为特征名称，value为特征值
     */
    private Map<String, Double> getTorrentVector(TorrentMetadataExt torrent) {
        String torrentId = torrent.getTorrentId();

        // 先从缓存中查找
        if (torrentVectors.containsKey(torrentId)) {
            return torrentVectors.get(torrentId);
        }

        // 构建特征向量
        Map<String, Double> vector = new HashMap<>();

        // 添加标签特征
        if (torrent.getTags() != null) {
            torrent.getTags().forEach(tag -> vector.put("tag_" + tag, 1.0));
        }

        // 添加语言特征
        if (torrent.getLanguage() != null) {
            vector.put("language_" + torrent.getLanguage(), 1.0);
        }

        // 添加分类特征
        if (torrent.getTorrentCategoryId() != null) {
            vector.put("category_" + torrent.getTorrentCategoryId(), 1.0);
        }

        // 添加字幕特征
        if (torrent.getSubtitles() != null) {
            torrent.getSubtitles().forEach(sub -> vector.put("subtitle_" + sub, 1.0));
        }

        // 添加质量特征
        if (torrent.getQuality() != null) {
            vector.put("quality_" + torrent.getQuality(), 1.0);
        }

        // 缓存结果
        torrentVectors.put(torrentId, vector);
        return vector;
    }

    /**
     * 根据行为类型获取权重
     *
     * 不同的用户行为表示不同的兴趣强度：
     * - DOWNLOAD（下载）：权重最高，表示强烈兴趣
     * - LIKE（点赞）：权重较高
     * - RATE（评分）：权重中等
     * - VIEW（查看）：权重较低
     * - 其他：权重最低
     *
     * @param behaviorType 行为类型
     * @return 对应的权重值
     */
    private double getBehaviorWeight(String behaviorType) {
        switch (behaviorType) {
            case "DOWNLOAD": return 5.0;  // 下载行为权重最高
            case "LIKE": return 3.0;      // 点赞行为权重较高
            case "RATE": return 2.0;      // 评分行为权重中等
            case "VIEW": return 1.0;      // 查看行为权重较低
            default: return 0.5;          // 其他行为权重最低
        }
    }

    /**
     * 构建推荐条目列表
     *
     * 将排序后的推荐结果转换为标准的推荐响应格式
     *
     * @param sortedItems 排序后的推荐项目列表
     * @return 推荐条目列表，包含torrentId、分数、算法名称和推荐理由
     */
    private List<Map<String, Object>> buildRecommendationItems(List<Map.Entry<String, Double>> sortedItems) {
        List<Map<String, Object>> recommendations = new ArrayList<>();
        for (Map.Entry<String, Double> entry : sortedItems) {
            String torrentId = entry.getKey();
            double score = entry.getValue();
            Optional<TorrentMetadataExt> metadata = torrentMetadataExtRepository.findByTorrentId(torrentId);
            if (metadata.isPresent()) {
                Map<String, Object> item = new HashMap<>();
                item.put("torrentId", torrentId);
                item.put("score", score);
                item.put("algorithm", "content-based");
                item.put("reason", "与您的兴趣偏好相符");
                recommendations.add(item);
            } else {
                logger.warn("Metadata not found for torrentId {} during content-based item building. Item skipped.", torrentId);
            }
        }
        return recommendations;
    }

    /**
     * 清除向量缓存
     *
     * 当内容元数据更新时，需要清除缓存以确保特征向量的准确性
     */
    public void clearVectorCache() {
        logger.info("Clearing content-based torrent vector cache. Current size: {}", torrentVectors.size());
        this.torrentVectors.clear();
    }
}